package de.rj.urwalking.services;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;

import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.PointF;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import de.rj.urwalking.R;
import de.rj.urwalking.config.Config;
import de.rj.urwalking.util.StepDetector;
import de.rj.urwalking.util.StepDetector.StepListener;

public class SensorLoggerService extends Service implements SensorEventListener, LocationListener, StepListener {

	private static final String TAG = Config.TAG;
	private static final boolean DEBUG = Config.DEBUG;
	private static final long ABOUT_FORTY_YEARS_IN_NANOSECONDS = 1300000000000000000L;
	private static final long NANOSECONDS_IN_A_MILLISECOND = 1000000L;

	private LocationManager mLocationManager;
	private SensorManager mSensorManager;
	private StepDetector stepDetector;
	private PrintWriter mLogger;

	private HashMap<Integer, Long> timestampsPerSensor = new HashMap<Integer, Long>();
	private long lastGpsTimestamp = 0;

	private int[] sensorIds = new int[] { Sensor.TYPE_MAGNETIC_FIELD, Sensor.TYPE_ACCELEROMETER, Sensor.TYPE_GYROSCOPE,
			Sensor.TYPE_LINEAR_ACCELERATION, Sensor.TYPE_ROTATION_VECTOR, Sensor.TYPE_ORIENTATION };

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		super.onStartCommand(intent, flags, startId);

		NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this).setSmallIcon(R.drawable.ic_status)
				.setContentIntent(PendingIntent.getActivity(this, 0, intent, 0)).setContentTitle("URwalking")
				.setContentText("Experiment läuft");

		startForeground(1, mBuilder.build());

		if (DEBUG) Log.d(TAG, "starting service");
		initLogging(intent.getStringExtra("logFilename"));
		initGPS();
		registerAvailableSensors();
		initStepDetector();

		return START_REDELIVER_INTENT;
	}

	@Override
	public void onDestroy() {
		Log.i(TAG, "Destroy the SensorLoggerService");
		super.onDestroy();

		mSensorManager.unregisterListener(this);
	}

	/**
	 * Creates a log file and opens it for writing.
	 */

	private void initLogging(String logFilename) {

		File path = new File(Environment.getExternalStorageDirectory(), Config.FOLDER_NAME);
		path.mkdirs();

		File logFile = new File(path, logFilename);

		try {
			mLogger = new PrintWriter(new FileWriter(logFile, false));
			mLogger.print("tseventmilliseconds");
			mLogger.print(Config.SENSORDATA_SEPARATOR);
			mLogger.print("tssystemmilliseconds");
			mLogger.print(Config.SENSORDATA_SEPARATOR);
			mLogger.print("tsconvertedmilliseconds");
			mLogger.print(Config.SENSORDATA_SEPARATOR);			
			mLogger.print("sensor_type");
			mLogger.print(Config.SENSORDATA_SEPARATOR);
			mLogger.print("accuracy");
			mLogger.print(Config.SENSORDATA_SEPARATOR);
			mLogger.print("value1");
			mLogger.print(Config.SENSORDATA_SEPARATOR);
			mLogger.print("value2");
			mLogger.print(Config.SENSORDATA_SEPARATOR);
			mLogger.print("value3");
			mLogger.print(Config.SENSORDATA_SEPARATOR);
			mLogger.print("value4");
			mLogger.print(Config.SENSORDATA_SEPARATOR);
			mLogger.print("value5");
			mLogger.print(Config.SENSORDATA_SEPARATOR);
			mLogger.println();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void initGPS() {
		mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
		startRequestingLocationUpdates();
	}

	private void registerAvailableSensors() {

		mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);

		for (int sensorId : sensorIds) {

			Sensor sensor = mSensorManager.getDefaultSensor(sensorId);
			if (sensor != null) {
				mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL);
				if (DEBUG) Log.d(TAG, "registered sensor for id " + sensorId);

			} else {
				if (DEBUG) Log.d(TAG, "didn't find a sensor for id " + sensorId);
			}
		}
	}

	private void initStepDetector() {
		if (mSensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION) != null
				&& mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR) != null) {
			stepDetector = new StepDetector(this, this);
			if (DEBUG) Log.d(TAG, "Enabled stepdetector logging");
		} else {
			if (DEBUG)
				Log.d(TAG, "Stepdetector not available: Linear Acceleration and Rotation Vector Sensors needed!");
		}

	}

	private void startRequestingLocationUpdates() {
		if (mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
			mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);
		}
		if (mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
			mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, this);
		}
	}

	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}

	@Override
	public void onAccuracyChanged(Sensor arg0, int arg1) {
	}

	@Override
	public void onSensorChanged(SensorEvent event) {

		int type = event.sensor.getType();
		// first reading for this sensor
		if (timestampsPerSensor.get(type) == null) {
			timestampsPerSensor.put(type, event.timestamp);
			logSensorEvent(event);
		}
		// last recorded reading was long enough ago
		else if (event.timestamp - timestampsPerSensor.get(type) > Config.SENSOR_DELAY_NANOSECONDS) {
			timestampsPerSensor.put(type, event.timestamp);
			logSensorEvent(event);
		} else {
			// do nothing, last sensor reading was not long enough ago
		}		
	}

	private void logSensorEvent(SensorEvent event) {
		int accuracy = 200;
		if(event.sensor.getType()==11){
			if(event.accuracy<0 || event.accuracy>3){
				accuracy = 200;
			}
			else{
				accuracy = event.accuracy;
			}
		}
		else{
			accuracy = event.accuracy;
		}
		mLogger.print(event.timestamp);
		mLogger.print(Config.SENSORDATA_SEPARATOR);
		mLogger.print(this.getSystemTs());
		mLogger.print(Config.SENSORDATA_SEPARATOR);			
		mLogger.print(calculateTimestamp(event.timestamp));
		mLogger.print(Config.SENSORDATA_SEPARATOR);
		mLogger.print(event.sensor.getType());
		mLogger.print(Config.SENSORDATA_SEPARATOR);
		mLogger.print(accuracy);
		mLogger.print(Config.SENSORDATA_SEPARATOR);

		for (int i = 0; i < event.values.length; ++i) {
			mLogger.print(Float.toString(event.values[i]));
			mLogger.print(Config.SENSORDATA_SEPARATOR);
		}
		mLogger.println();
	}

	/**
	 * Calculates an absolute timestamp from the event timestamp, which is created differently across devices.
	 * http://stackoverflow.com/questions/5500765/accelerometer-sensorevent-timestamp
	 * 
	 * @param timestamp
	 *            the timestamp as we got it from the sensor event
	 * @return a timestamp in milliseconds since the epoch
	 */
	private long calculateTimestamp(long timestamp) {
		long absoluteTimestampMillis;
		if (timestamp > ABOUT_FORTY_YEARS_IN_NANOSECONDS) {
			// Timestamp seems to be in nanoseconds since the epoch already, no need to convert it.
			// This is the case on newer devices like the Nexus 7.
			absoluteTimestampMillis = timestamp / NANOSECONDS_IN_A_MILLISECOND;
		} else {
			// Timestamp seems to be in nanoseconds since the device booted, so we have to convert it.
			// This is the case on older devices like the HTC Desire (even when running Android 4.2.2).
			absoluteTimestampMillis = System.currentTimeMillis() + (timestamp - System.nanoTime())
					/ NANOSECONDS_IN_A_MILLISECOND;
		}
		return absoluteTimestampMillis;
	}
	
	private final long getSystemTs(){
		return System.currentTimeMillis();
	}
	

	@Override
	public void onLocationChanged(Location location) {
		if (location.getTime() - lastGpsTimestamp > Config.SENSOR_DELAY_MILLISECONDS) {
			logLocation(location);
		}
	}

	@Override
	public void onProviderDisabled(String provider) {
	}

	@Override
	public void onProviderEnabled(String provider) {
	}

	@Override
	public void onStatusChanged(String provider, int status, Bundle extras) {
	}

	private void logLocation(Location location) {		
		Bundle locationExtras = location.getExtras();
		int numSats = -1;
		if(locationExtras.containsKey("satellites"))
			numSats = locationExtras.getInt("satellites");
		mLogger.print(location.getTime());
		mLogger.print(Config.SENSORDATA_SEPARATOR);		
		mLogger.print(this.getSystemTs());
		mLogger.print(Config.SENSORDATA_SEPARATOR);	
		// No need to calculate timestamps, the one we get here is already in milliseconds since the epoch on all
		// devices. So let's just log the time once more in order not to break the .csv format.		
		mLogger.print(location.getTime());
		mLogger.print(Config.SENSORDATA_SEPARATOR);
		mLogger.print(location.getProvider());
		mLogger.print(Config.SENSORDATA_SEPARATOR);
		mLogger.print(location.getAccuracy());
		mLogger.print(Config.SENSORDATA_SEPARATOR);
		mLogger.print(location.getLatitude());
		mLogger.print(Config.SENSORDATA_SEPARATOR);
		mLogger.print(location.getLongitude());
		mLogger.print(Config.SENSORDATA_SEPARATOR);
		mLogger.print(location.getAltitude());
		mLogger.print(Config.SENSORDATA_SEPARATOR);
		mLogger.print(location.getSpeed());
		mLogger.print(Config.SENSORDATA_SEPARATOR);
		mLogger.print(location.getBearing());
		mLogger.print(Config.SENSORDATA_SEPARATOR);
		mLogger.print(numSats);
		mLogger.print(Config.SENSORDATA_SEPARATOR);		
		mLogger.println();
	}

	@Override
	public void onStep(PointF direction, float length,float variance, long timestamp) {
		mLogger.print(timestamp); 
		mLogger.print(Config.SENSORDATA_SEPARATOR);
		mLogger.print(this.getSystemTs());
		mLogger.print(Config.SENSORDATA_SEPARATOR);			
		mLogger.print(timestamp);  //'cause timestamp is in millis
		mLogger.print(Config.SENSORDATA_SEPARATOR);
		mLogger.print(Config.SENSOR_ID_STEPDETECTOR); // "sensor" id
		mLogger.print(Config.SENSORDATA_SEPARATOR);
		mLogger.print(0); // accuracy (may be available in the future)
		mLogger.print(Config.SENSORDATA_SEPARATOR);
		mLogger.print(direction.x); // x value of the direction vector (only works correctly in portrait mode at the
									// moment)
		mLogger.print(Config.SENSORDATA_SEPARATOR);
		mLogger.print(direction.y); // y value of the direction vector (only works correctly in portrait mode at the
									// moment)
		mLogger.print(Config.SENSORDATA_SEPARATOR);
		mLogger.print(length); // step length (always set to 0.8m at the moment)
		mLogger.println();

	}

	
	@Override
	public void onFilteredStep(Location location) {
		// TODO Auto-generated method stub
		
	}
}
